/*
	Raj Akula
	This program does matrix compare, add, subtract, multiply with other matrix
	as well as with scalar values and an additional mod, division with scalar values.

*/

#include <iostream>
#include <vector> 
using namespace std;

//Namespace NKU Math
namespace nkumath
{
	//Template for matrix class
	template<typename T, size_t ROWS, size_t COLS>
	class Matrix
	{
	private:
		vector<vector<T> > array;
	public:

		//Constructor for Matrix class
		Matrix(T init = T())
		{
			array.resize(ROWS);
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					array[i].push_back(init);
				}
			}
		}

		//operator to return value
		vector<T> &operator[](int row)
		{
			return array[row];
		}

		//Same operator but returns a const
		const vector<T> &operator[](int row)const
		{
			return array[row];
		}
		
		//Addition with everything in matrix
		Matrix &operator+=(const T & rhs)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					array[i][j] += rhs;
				}
			}
			return *this;
		}

		//Subtraction with everything in matrix
		Matrix &operator-=(const T & rhs)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					array[i][j] -= rhs;
				}
			}
			return *this;
		}

		//Multiplication with everything in matrix
		Matrix &operator*=(const T & rhs)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					array[i][j] *= rhs;
				}
			}
			return *this;
		}

		//Division with everything in matrix
		Matrix &operator/=(const T & rhs)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					array[i][j] /= rhs;
				}
			}
			return *this;
		}

		//Mod with everything in matrix
		Matrix &operator%=(const T & rhs)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					array[i][j] %= rhs;
				}
			}
			return *this;
		}

		//Print out your vector of objects if supported
		void print(ostream &out) const
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					out << array[i][j] << " ";
				}
				out << endl;
			}
			out << endl;
		}

		//Checks if all values in two Matrix class are equal
		bool operator==(const Matrix<T, ROWS, COLS> m)
		{
			bool equal = true;
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					if((*this)[i][j] != m[i][j])
					{
						equal = false;
						break;
					}
				}
			}
			return equal;
		}

		//Matrix add just add at each element 
		Matrix & matrixAdd(const Matrix<T, ROWS, COLS> m1, const Matrix<T, ROWS, COLS> m2)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					(*this)[i][j] = m1[i][j] + m2[i][j];
				}
			}
			return *this;
		}

		//Same as add but just change to minus
		Matrix & matrixSubtract(const Matrix<T, ROWS, COLS> m1, const Matrix<T, ROWS, COLS> m2)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					(*this)[i][j] = m1[i][j] - m2[i][j];
				}
			}
			return *this;
		}

		//Multiplication made sure m1 cols and m2 rows are same then add a each row * col element 
		//kept a variable to keep track of everything
		template <size_t INNER>
		Matrix & matrixMult(const Matrix<T, ROWS, INNER> & m1, const Matrix<T, INNER, COLS> & m2)
		{
			for(size_t i = 0; i < ROWS; i++)
			{
				for(size_t j = 0; j < COLS; j++)
				{
					T sum = T();
					for(size_t k = 0; k < INNER; k++)
					{
						sum += m1[i][k] * m2[k][j];
					}
					(*this)[i][j] = sum;
				}
			}
			return *this;	
		}

	};

	//call a function defined
	template <typename T, size_t ROWS, size_t COLS>
	void matrixAdd(const Matrix<T, ROWS, COLS> &m1, const Matrix<T, ROWS, COLS> &m2, Matrix<T, ROWS, COLS> &result)
	{
		result.matrixAdd(m1, m2);
	}

	//call defined function
	template <typename T, size_t ROWS, size_t COLS>
	Matrix<T, ROWS, COLS> operator+(const Matrix<T, ROWS, COLS> lhs, const Matrix<T, ROWS, COLS> &rhs)
	{
		Matrix<T, ROWS, COLS> temp;
		matrixAdd(lhs, rhs, temp);
		return temp;
	}

	//call defined function
	template <typename T, size_t ROWS, size_t COLS>
	void matrixSubtract(const Matrix<T, ROWS, COLS> &m1, const Matrix<T, ROWS, COLS> &m2, Matrix<T, ROWS, COLS> &result)
	{
		result.matrixSubtract(m1, m2);
	}
	
	//call defined function
	template <typename T, size_t ROWS, size_t COLS>
	Matrix<T, ROWS, COLS> operator-(const Matrix<T, ROWS, COLS> lhs, const Matrix<T, ROWS, COLS> &rhs)
	{
		Matrix<T, ROWS, COLS> temp;
		matrixSubtract(lhs, rhs, temp);
		return temp;
	}

	//call defined function
	template <typename T, size_t INNER, size_t ROWS, size_t COLS>
	void matrixMult(const Matrix<T, ROWS, INNER> &m1, const Matrix<T, INNER, COLS> &m2, Matrix<T, ROWS, COLS> &result)
	{
		result.matrixMult(m1, m2);
	}

	//call defined function
	template <typename T, size_t ROWS, size_t COLS, size_t INNER>
	Matrix<T, ROWS, COLS> operator*(const Matrix<T, ROWS, INNER> lhs, const Matrix<T, INNER, COLS> &rhs)
	{
		Matrix<T, ROWS, COLS> temp;
		matrixMult(lhs, rhs, temp);
		return temp;
	}

	//call defined function
	template <typename T, size_t ROWS, size_t COLS>
	ostream &operator<<(ostream & out, const Matrix<T, ROWS, COLS> &m)
	{
		m.print(out);
		return out;
	}
}